package com.esfak47.mediafetcher.app;

import com.esfak47.common.extension.ExtensionLoader;
import com.esfak47.common.logger.Logger;
import com.esfak47.common.logger.LoggerFactory;
import com.esfak47.common.utils.PluginsClassLoader;
import com.esfak47.mediafetcher.app.event.EventBusListenerDelegate;
import com.esfak47.mediafetcher.app.event.EventHandlerDelegate;
import com.esfak47.mediafetcher.app.event.SystemEventListener;
import com.esfak47.mediafetcher.app.extension.SpringExtensionFactory;
import com.esfak47.mediafetcher.app.service.AppEventSender;
import com.esfak47.mediafetcher.core.constants.Constants;
import com.esfak47.mediafetcher.core.event.Event;
import com.esfak47.mediafetcher.core.event.EventGroupEnum;
import com.esfak47.mediafetcher.core.event.EventHandler;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.support.ServletRequestHandledEvent;

import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author tony
 */
@SpringBootApplication
@EnableCaching

public class Application {
    private static final Logger log = LoggerFactory.getLogger(Application.class);
    public static ClassLoader pluginClassLoader;
    public static EventBus eventBus;
    private static SmartApplicationListener applicationListener;
    private static StaticApplicationContext staticApplicationContext;

    static {
        staticApplicationContext = new StaticApplicationContext();
        staticApplicationContext.registerSingleton("eventSender", AppEventSender.class);

        SpringExtensionFactory.addApplicationContext(staticApplicationContext);
        initPluginsLoader();
        initEventBus();
        initSystemListener();
    }

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Application.class);
        springApplication.addListeners(applicationListener);
        ConfigurableApplicationContext run = springApplication.run(args);
        SpringExtensionFactory.addApplicationContext(run);
        eventBus.register(new SystemEventListener(run));
        ExtensionLoader<EventHandler> extensionLoader =
            ExtensionLoader.getExtensionLoader(EventHandler.class, Application.pluginClassLoader);
        Set<String> supportedExtensions = extensionLoader
            .getSupportedExtensions();

        final Stream<EventHandler> eventHandlerStream = supportedExtensions.stream()
            .map(extensionLoader::getExtension);
        eventHandlerStream.forEach(eventHandler -> {log.info("find "+eventHandler.getClass()+" for eventHandler");});

        Set<EventHandler> eventHandlers = eventHandlerStream
            .map(EventHandlerDelegate::new)
            .collect(Collectors.toSet());

        eventBus.register(new EventBusListenerDelegate(eventHandlers));
    }

    private static void initSystemListener() {
        applicationListener = new SmartApplicationListener() {

            @Override
            public int getOrder() {
                return 0;
            }

            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                if (event instanceof ApplicationPreparedEvent) {
                    eventBus.post(new Event(EventGroupEnum.APP, "prepared", null));
                } else if (event instanceof ContextRefreshedEvent) {
                    eventBus.post(new Event(EventGroupEnum.APP, "refreshed", null));
                }  else if (event instanceof ApplicationReadyEvent) {
                    eventBus.post(new Event(EventGroupEnum.APP, "ready", null));
                } else if (event instanceof ServletRequestHandledEvent) {
                    final ServletRequestHandledEvent event1 = (ServletRequestHandledEvent)event;
                    eventBus.post(new Event(EventGroupEnum.APP, "web", event1.getDescription()));
                }

            }

            @Override
            public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
                return true;
            }

            @Override
            public boolean supportsSourceType(Class<?> sourceType) {
                return true;
            }
        };

    }

    private static void initEventBus() {
        log.info("start to init system event bus");
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("event");
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(1024);
        threadPoolTaskExecutor.setKeepAliveSeconds(5000);
        threadPoolTaskExecutor.setMaxPoolSize(20);
        threadPoolTaskExecutor.initialize();

        eventBus = new AsyncEventBus(threadPoolTaskExecutor);
        AppEventSender.eventBus = eventBus;

    }

    private static void initPluginsLoader() {
        log.info("start to init plugins");
        try {
            pluginClassLoader =
                new PluginsClassLoader(Constants.PLUGINS_DIR, Constants.MODULES_DIR);
        } catch (IOException e) {
            System.err.println("find to init due to IOException");
            System.exit(1);
        }
    }

}
